package core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;

public class Factory {

    public int money;
    public final int WORK_HOUR;
    public final int MAX_MACHINES;
    public final int MAX_RENTABLE_MACHINES_BY_FACTORY;
    public final int TRUCK_CAPACITY;
    //daftar semua mesin yang ada
    private Machine[] machines;
    private Hashtable<Integer, Machine> machineHash = new Hashtable<Integer, Machine>();
    //daftar mesin yang disewa oleh pabrik
    private ArrayList<Machine> rentedMachines;

    //daftar semua jenis produk yang ada
    private Factory() {
        WORK_HOUR = 0;
        MAX_MACHINES = 0;
        MAX_RENTABLE_MACHINES_BY_FACTORY = 0;
        TRUCK_CAPACITY = 0;
        rentedMachines = new ArrayList<Machine>();
    }

    private Factory(int money, int workHour, int maxMachines, int maxRentableMachinesByFactory, int truckCapacity) {
        this.money = money;
        this.WORK_HOUR = workHour;
        this.MAX_MACHINES = maxMachines;
        this.MAX_RENTABLE_MACHINES_BY_FACTORY = maxRentableMachinesByFactory;
        rentedMachines = new ArrayList<Machine>();
        TRUCK_CAPACITY = truckCapacity;
        machines = new Machine[maxMachines];
    }

    public static void createInstance(int money, int workHour, int maxMachines, int maxRentableMachinesByFactory, int truckCapacity) {
        FactoryHolder.INSTANCE = new Factory(money, workHour, maxMachines, maxRentableMachinesByFactory, truckCapacity);
    }

    public static Factory getInstance() {
        return FactoryHolder.INSTANCE;
    }

    public Machine getMachineByType(int type) {
        return (Machine) machineHash.get(type);
    }

    public Machine[] getAllRentableMachines() {
        return machines;
    }

    public void addMachine(Machine machine) {
        boolean found = false;
        int idxEmpty = machines.length;
        for (int i = 0; i < machines.length; i++) {
            if (machines[i] == null) {
                idxEmpty = i;
                break;
            }
            if (machines[i].TYPE == machine.TYPE) {
                found = true;
                break;
            }
        }
        if (!found) {
            machines[idxEmpty] = machine;
        }
        //taro hash
        if (!machineHash.containsKey(machine.TYPE)) {
            machineHash.put(machine.TYPE, machine);
        }
    }

    public void rentMachine(Machine machine) {
        boolean found = false;
        for (int i = 0; i < rentedMachines.size(); i++) {
            if (rentedMachines.get(i).TYPE == machine.TYPE) {
                found = true;
                break;
            }
        }
        if (!found) {
            rentedMachines.add(machine);
        }
    }

    public static void simulate(int[] individu, int StartHour, PathFinding PF) {
        boolean[] MachineAvailable = new boolean[Factory.getInstance().MAX_MACHINES];
        for (int i = 0; i < MachineAvailable.length; i++) {
            MachineAvailable[i] = false;
        }
        HashMap<Integer, Integer> ProductProduce = new HashMap<Integer, Integer>();
        int Hour = StartHour;
        HashMap<Integer, Integer> RestNeed = new HashMap<Integer, Integer>();
        HashMap<Integer, Integer> ConsecutiveWork = new HashMap<Integer, Integer>();
        HashMap<Integer, Integer> TotalWork = new HashMap<Integer, Integer>();
                
        int Minute = 0;
        int MachineTime = 60;
        int CurrentMachine = 0;
        boolean AbleProduce = true; // indikator mesin selanjutnya bisa bekerja apa tidak
        boolean LastAbleProduce = false; // indikator mesin sebelumnya bisa produksi atau tidak

        PF.process(Factory.getInstance().TRUCK_CAPACITY, individu);
        for (int i = 0; i < PF.timeMachine.length; i++) {
            System.out.println("ID=" + PF.timeMachine[i].x);
            System.out.println("Time=" + PF.timeMachine[i].y);
        }

        Output.getInstance().println("");
        Output.getInstance().println("------------------------------");
        Output.getInstance().println("Pukul " + Hour + ":" + Minute);
        Output.getInstance().println("------------------------------");
        Output.getInstance().println("Pabrik mulai bekerja");
        while (Hour < Factory.getInstance().WORK_HOUR + StartHour) {                       
            
            if(MachineTime==60 && CurrentMachine>0 && MachineAvailable[individu[CurrentMachine-1]-1]) {
                Output.getInstance().println("");
                Output.getInstance().println("------------------------------");
                Output.getInstance().println("Pukul " + Hour + ":" + Minute);
                Output.getInstance().println("------------------------------");
                if (LastAbleProduce == false) {
                    Output.getInstance().println("Mesin "+ individu[CurrentMachine-1] +" tidak menghasilkan produk");
                } else if (ProductProduce.containsKey(individu[CurrentMachine-1]) && LastAbleProduce == true) {
                    Machine m = Factory.getInstance().getMachineByType(individu[CurrentMachine-1]);
                    int increment = ConsecutiveWork.get(individu[CurrentMachine-1]) > m.MAX_CONTINUED_WORK_HOUR ? (int) (m.PRODUCTIVITY*(m.COST_OVERLOAD/100.0)) : m.PRODUCTIVITY; 
                    
                    if(ConsecutiveWork.get(individu[CurrentMachine-1]) > m.MAX_CONTINUED_WORK_HOUR)
                        Output.getInstance().println("Mesin " + individu[CurrentMachine-1] + " mengalami cost overload");
                    
                    ProductProduce.put(individu[CurrentMachine-1], ProductProduce.get(individu[CurrentMachine-1]) + increment);
                    Output.getInstance().println("Mesin " + individu[CurrentMachine-1] + " menghasilkan produk sebanyak = " + increment);
                } else {
                    Machine m = Factory.getInstance().getMachineByType(individu[CurrentMachine-1]);
                    int increment = ConsecutiveWork.get(individu[CurrentMachine-1]) > m.MAX_CONTINUED_WORK_HOUR ? (int) (m.PRODUCTIVITY*(m.COST_OVERLOAD/100.0)) : m.PRODUCTIVITY; 
                    
                    if(ConsecutiveWork.get(individu[CurrentMachine-1]) > m.MAX_CONTINUED_WORK_HOUR)
                        Output.getInstance().println("Mesin " + individu[CurrentMachine-1] + " mengalami cost overload");
                    
                    ProductProduce.put(individu[CurrentMachine-1], increment);
                    Output.getInstance().println("Mesin " + individu[CurrentMachine-1] + " menghasilkan produk sebanyak = " + increment);
                }
                Output.getInstance().println("jam Kerja berturut-turut = "+ConsecutiveWork);
                Output.getInstance().println("RestNeed = "+RestNeed);
                Output.getInstance().println("Produk = " + ProductProduce);
                Output.getInstance().println("total work = " + TotalWork);
            }
            
            boolean machineArrive = false;
            for (int i = 0; i < PF.timeMachine.length; i++) {
                if (PF.timeMachine[i].y==(Hour-StartHour)*60+Minute){
                    if(!machineArrive) {
                        Output.getInstance().println("");
                        Output.getInstance().println("------------------------------");
                        Output.getInstance().println("Pukul " + Hour + ":" + Minute);
                        Output.getInstance().println("------------------------------");
                        machineArrive = true;
                    }
                    MachineAvailable[PF.timeMachine[i].x-1]=true;
                    Output.getInstance().println("Mesin "+PF.timeMachine[i].x+" datang");
                }
            }
            
            if(MachineTime == 60) {
                if(TotalWork.containsKey(individu[CurrentMachine]) && TotalWork.get(individu[CurrentMachine])>=Factory.getInstance().getMachineByType(individu[CurrentMachine]).MAX_WORK_HOUR) {
                    //jika mesin tersebut telah bekerja dengan jam kerja melebihi total jam kerja yang diperbolehkan, mesin tersebut gak bisa kerja                    
                    AbleProduce = false;

                } else if (Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY == -1) {
                    //System.out.println("Mesin "+individu[CurrentMachine]+" tidak butuh bahan");
                    AbleProduce = true;
                } else if (Hour < Factory.getInstance().WORK_HOUR + StartHour && ProductProduce.containsKey(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY) && ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY) >= Factory.getInstance().getMachineByType(individu[CurrentMachine]).INGREDIENT_COUNT) {
                    //System.out.println("Jumlah bahan "+Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY+" = "+ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY));
                    //System.out.println("Jumlah seharusnya "+(ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY)-Factory.getInstance().getMachineByType(individu[CurrentMachine]).INGREDIENT_COUNT));                        
                    //System.out.println("Jumlah bahan "+Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY+" = "+ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY));
                    AbleProduce = true;
                } else {                                        
                    AbleProduce = false; // mesin berikutnya gak bisa kerja karena kekurangan bahan
                }
            }
            
            if (MachineAvailable[individu[CurrentMachine]-1]) {
                if (MachineTime == 60 && AbleProduce) {                                                            
                    //tepat sebelum bekerja, makan bahan yang dibutuhkan
                    if(Hour < Factory.getInstance().WORK_HOUR + StartHour && ProductProduce.containsKey(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY) && ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY) >= Factory.getInstance().getMachineByType(individu[CurrentMachine]).INGREDIENT_COUNT) {
                        ProductProduce.put(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY, (ProductProduce.get(Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY) - Factory.getInstance().getMachineByType(individu[CurrentMachine]).INGREDIENT_COUNT));
                        Output.getInstance().println("Mesin " + individu[CurrentMachine] + " makan " + Factory.getInstance().getMachineByType(individu[CurrentMachine]).DEPENDENCY + " sebanyak " + Factory.getInstance().getMachineByType(individu[CurrentMachine]).INGREDIENT_COUNT);
                    }                    
                    
                    Output.getInstance().println("Mesin " + individu[CurrentMachine] + " bekerja");
                    LastAbleProduce = true;
                    RestAllMachineExcept(RestNeed, Factory.getInstance().getMachineByType(individu[CurrentMachine]));
                    
                    //increment jam kerja berturut2 mesin tersebut
                    if (ConsecutiveWork.containsKey(individu[CurrentMachine])){
                        ConsecutiveWork.put(individu[CurrentMachine], ConsecutiveWork.get(individu[CurrentMachine])+1);
                    } else {
                        ConsecutiveWork.put(individu[CurrentMachine], 1);
                    }
                    
                    //increment jam kerja total mesin tersebut
                    int newTotalWorkVal = TotalWork.containsKey(individu[CurrentMachine]) ? TotalWork.get(individu[CurrentMachine]) + 1 : 1;
                    TotalWork.put(individu[CurrentMachine], newTotalWorkVal);
                    
                    //syarat2 reset jam kerja terus menerus : 
                    // jika jam kerja mesin tersebut belum melebihi beban kerja terus menerus maksimum, reset jam kerja berturut2 mesin tersebut
                    if ((CurrentMachine>0 && ConsecutiveWork.containsKey(individu[CurrentMachine-1]) && ConsecutiveWork.get(individu[CurrentMachine-1]) <= Factory.getInstance().getMachineByType(individu[CurrentMachine-1]).MAX_CONTINUED_WORK_HOUR && individu[CurrentMachine]!=individu[CurrentMachine-1]) 
                            || (CurrentMachine>0 && !ConsecutiveWork.containsKey(individu[CurrentMachine-1]))){                                                                                                
                        ConsecutiveWork.put(individu[CurrentMachine-1], 0);
                    }
                    // cek semua mesin, jika ada yang waktu istirahat nya lebih dari sama dengan REST_TIME maka reset jam kerja berturut2
                    ResetConsWorkIfRestSuff(RestNeed, ConsecutiveWork);
                } else if (MachineTime == 60 && AbleProduce == false) {
                    Output.getInstance().println("Mesin " + individu[CurrentMachine] + " tidak dapat bekerja");
                    LastAbleProduce = false;
                }
                
                if (MachineTime > 1) {
                    MachineTime--;                    
                }
                else if (MachineTime == 1) {                                        
                    MachineTime = 60;
                    if (CurrentMachine < individu.length - 1) {
                        CurrentMachine++;
                    }                                        
                }
            }
            if (Minute < 59) {
                Minute += 1;
            } else {
                Hour += 1;
                Minute = 0;
            }
        } 
        System.out.println(ProductProduce);
        Output.getInstance().println("");
        Output.getInstance().println("Uang awal = "+Factory.getInstance().money);
        Output.getInstance().println("Keuntungan yang diperoleh = "+ProfitCount(MachineAvailable, ProductProduce));
        Output.getInstance().println("Uang akhir = "+(Factory.getInstance().money+ProfitCount(MachineAvailable, ProductProduce)));
    }

    public static void RestAllMachineExcept(HashMap<Integer,Integer> restHour, Machine workingMachine) {
        for(Machine machine : Factory.getInstance().getAllRentableMachines()) {            
            if(machine.TYPE != workingMachine.TYPE) {
                // jika mesin sedang tidak bekerja, tambah counter istirahat +1
                if(restHour.containsKey(machine.TYPE)) 
                    restHour.put(machine.TYPE, restHour.get(machine.TYPE) + 1);
                else
                    restHour.put(machine.TYPE, 1);
            }else{
                // jika mesin bekerja, reset counter istirahat jadi 0                
                    restHour.put(machine.TYPE, 0);
            }
        }
    }
    
    public static void ResetConsWorkIfRestSuff(HashMap<Integer,Integer> restHour, HashMap<Integer,Integer> consWork) {
        for(Machine machine : Factory.getInstance().getAllRentableMachines()) {
            if(restHour.containsKey(machine.TYPE) && restHour.get(machine.TYPE)>=machine.REST_TIME) {
                //System.out.println(machine.TYPE + " " + restHour.get(machine.TYPE) + " " + machine.REST_TIME);
                consWork.put(machine.TYPE, 0);
            } 
        }
    }
    
    public static int ProfitCount(boolean[] MachineAvailable, HashMap<Integer,Integer> ProductProduce){
        int profit=0;
        int machinerent=0;
        
        for (int i=0; i<MachineAvailable.length; i++){
            if (MachineAvailable[i])
                machinerent+=Factory.getInstance().getMachineByType(i+1).RENT_COST;
            if (ProductProduce.containsKey(i+1)){
                profit += ProductProduce.get(i+1)*ProductList.getProductByType(i+1).SELLING_PRICE;
                //System.out.println((i+1) + " " + ProductProduce.get(i+1) + " " + ProductList.getProductByType(i+1).SELLING_PRICE + " " + ProductProduce.get(i+1)*ProductList.getProductByType(i+1).SELLING_PRICE);
            }
        }
        
        profit-=machinerent;
        
        return profit;
    }
    
    private static class FactoryHolder {

        private static Factory INSTANCE = new Factory();
    }

    public static void main(String[] args) {
        Parser.parse("input.txt");
        PathFinding PF = new PathFinding(1);
        int[] individu = {3,4,5,3,4,3,4,5,3,4,5,3,4,5,4,4,5,5};//Genetic.createRandomIndividu(Factory.getInstance().WORK_HOUR, Factory.getInstance().getAllRentableMachines());
        System.out.print("Individu ");
        for (int a : individu) {
            Output.getInstance().print(a + " ");
        }
        Output.getInstance().println("");
        Factory.simulate(individu, 7, PF);
    }
}
